package org.outlook.xnotepad;

/*
 * @(#)Notepad.java	1.31 05/11/17
 * 
 * Copyright (c) 2006 Sun Microsystems, Inc. All Rights Reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 * 
 * -Redistribution of source code must retain the above copyright notice, this
 *  list of conditions and the following disclaimer.
 * 
 * -Redistribution in binary form must reproduce the above copyright notice, 
 *  this list of conditions and the following disclaimer in the documentation
 *  and/or other materials provided with the distribution.
 * 
 * Neither the name of Sun Microsystems, Inc. or the names of contributors may 
 * be used to endorse or promote products derived from this software without 
 * specific prior written permission.
 * 
 * This software is provided "AS IS," without a warranty of any kind. ALL 
 * EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING
 * ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE
 * OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED. SUN MIDROSYSTEMS, INC. ("SUN")
 * AND ITS LICENSORS SHALL NOT BE LIABLE FOR ANY DAMAGES SUFFERED BY LICENSEE
 * AS A RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
 * DERIVATIVES. IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST 
 * REVENUE, PROFIT OR DATA, OR FOR DIRECT, INDIRECT, SPECIAL, CONSEQUENTIAL, 
 * INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED AND REGARDLESS OF THE THEORY 
 * OF LIABILITY, ARISING OUT OF THE USE OF OR INABILITY TO USE THIS SOFTWARE, 
 * EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
 * 
 * You acknowledge that this software is not designed, licensed or intended
 * for use in the design, construction, operation or maintenance of any
 * nuclear facility.
 */

/*
 * @(#)Notepad.java	1.31 05/11/17
 */

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.FileDialog;
import java.awt.Font;
import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.net.URL;
import java.util.Hashtable;
import java.util.Locale;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.StringTokenizer;
import java.util.Vector;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JToolBar;
import javax.swing.JViewport;
import javax.swing.KeyStroke;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.event.UndoableEditEvent;
import javax.swing.event.UndoableEditListener;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.JTextComponent;
import javax.swing.text.PlainDocument;
import javax.swing.text.Segment;
import javax.swing.text.TextAction;
import javax.swing.undo.CannotRedoException;
import javax.swing.undo.CannotUndoException;
import javax.swing.undo.UndoManager;

import org.apache.commons.lang.StringUtils;
import org.outlook.xnotepad.action.CommandAction;
import org.outlook.xnotepad.action.FindAction;

/**
 * Sample application using the simple text editor component that supports only
 * one font.
 * 
 * @author Timothy Prinzing
 * @version 1.31 11/17/05
 */
@SuppressWarnings("unchecked")
public class Notepad extends JPanel {

	private static final long serialVersionUID = 5975426032343907618L;

	private static ResourceBundle resources;
	private final static String EXIT_AFTER_PAINT = new String("-exit");
	private static boolean exitAfterFirstPaint;

	static {
		try {
			resources = ResourceBundle.getBundle("resources.Notepad", Locale
					.getDefault());
		} catch (MissingResourceException mre) {
			System.err.println("resources/Notepad.properties not found");
			System.exit(1);
		}
	}

	public void paintChildren(Graphics g) {
		super.paintChildren(g);
		if (exitAfterFirstPaint) {
			System.exit(0);
		}
	}

	Notepad() {
		super(true);

		// Force SwingSet to come up in the Cross Platform L&F
		try {
			UIManager.setLookAndFeel(UIManager
					.getCrossPlatformLookAndFeelClassName());
			// If you want the System L&F instead, comment out the above line
			// and
			// uncomment the following:
			//UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()
			// );
		} catch (Exception exc) {
			System.err.println("Error loading L&F: " + exc);
		}

		setBorder(BorderFactory.createEtchedBorder());
		setLayout(new BorderLayout());

		// create the embedded JTextComponent
		if(null == editor)
			editor = createEditor();
		// Add this as a listener for undoable edits.
		editor.getDocument().addUndoableEditListener(undoHandler);

		// install the command table
		commands = new Hashtable();
		Action[] actions = getActions();
		for (int i = 0; i < actions.length; i++) {
			Action a = actions[i];
			// commands.put(a.getText(Action.NAME), a);
			commands.put(a.getValue(Action.NAME), a);
		}

		JScrollPane scroller = new JScrollPane();
		JViewport port = scroller.getViewport();
		port.add(editor);
		try {
			String vpFlag = resources.getString("ViewportBackingStore");
			Boolean bs = Boolean.valueOf(vpFlag);
			port.setBackingStoreEnabled(bs.booleanValue());
		} catch (MissingResourceException mre) {
			// just use the viewport default
		}

		menuItems = new Hashtable();
		JPanel panel = new JPanel();
		panel.setLayout(new BorderLayout());
		panel.add("North", createToolbar());
		panel.add("Center", scroller);
		add("Center", panel);
		add("South", createStatusbar());
	}

	static Notepad notepad = null;

	public static void main(String[] args) {
		try {
			String vers = System.getProperty("java.version");
			if (vers.compareTo("1.1.2") < 0) {
				System.out.println("!!!WARNING: Swing must be run with a "
						+ "1.1.2 or higher version VM!!!");
			}
			if (args.length > 0 && args[0].equals(EXIT_AFTER_PAINT)) {
				exitAfterFirstPaint = true;
			}
			JFrame frame = new JFrame();
			frame.setTitle(resources.getString("Title"));
			frame.setBackground(Color.lightGray);
			frame.getContentPane().setLayout(new BorderLayout());
			notepad = new Notepad();
			frame.getContentPane().add("Center", notepad);
			frame.setJMenuBar(notepad.createMenubar());
			frame.addWindowListener(new AppCloser());
			frame.pack();
			frame.setSize(800, 600);
			frame.setVisible(true);
		} catch (Throwable t) {
			System.out.println("uncaught exception: " + t);
			t.printStackTrace();
		}
	}

	/**
	 * Fetch the list of actions supported by this editor. It is implemented to
	 * return the list of actions supported by the embedded JTextComponent
	 * augmented with the actions defined locally.
	 */
	public Action[] getActions() {
		return TextAction.augmentList(editor.getActions(), defaultActions);
	}

	/**
	 * Create an editor to represent the given document.
	 */
	protected JTextComponent createEditor() {
		JTextComponent c = new JTextArea();
		c.setDragEnabled(true);
		c.setFont(new Font("monospaced", Font.PLAIN, 12));
		return c;
	}

	/**
	 * Fetch the editor contained in this panel
	 */
	public JTextComponent getEditor() {
		return editor;
	}

	/**
	 * To shutdown when run as an application. This is a fairly lame
	 * implementation. A more self-respecting implementation would at least
	 * check to see if a save was needed.
	 */
	protected static final class AppCloser extends WindowAdapter {
		public void windowClosing(WindowEvent e) {
			System.exit(0);
		}
	}

	/**
	 * Find the hosting frame, for the file-chooser dialog.
	 */
	public Frame getFrame() {
		for (Container p = getParent(); p != null; p = p.getParent()) {
			if (p instanceof Frame) {
				return (Frame) p;
			}
		}
		return null;
	}

	/**
	 * This is the hook through which all menu items are created. It registers
	 * the result with the menuitem hashtable so that it can be fetched with
	 * getMenuItem().
	 * 
	 * @see #getMenuItem
	 */
	protected JMenuItem createMenuItem(String cmd) {
		JMenuItem mi = new JMenuItem(getResourceString(cmd + labelSuffix));
		URL url = getResource(cmd + imageSuffix);
		if (url != null) {
			mi.setHorizontalTextPosition(JButton.RIGHT);
			mi.setIcon(new ImageIcon(url));
		}
		String astr = getResourceString(cmd + actionSuffix);
		if (astr == null) {
			astr = cmd;
		}
		mi.setActionCommand(astr);
		try {
			final String keyStrokeStr = getResourceString(cmd + keySuffix);
			if(StringUtils.isNotBlank(keyStrokeStr)) {
				KeyStroke keyStroke = KeyStroke.getKeyStroke(keyStrokeStr);
				mi.setAccelerator(KeyStroke.getKeyStroke(keyStroke.getKeyCode(),ActionEvent.CTRL_MASK));
			}
		} catch (Exception e) {
			e.printStackTrace();
			System.out.println(cmd);
		}
		Action a = getAction(astr);
		if (a != null) {
			mi.addActionListener(a);
			a.addPropertyChangeListener(createActionChangeListener(mi));
			mi.setEnabled(a.isEnabled());
		} else {
			mi.setEnabled(false);
		}
		menuItems.put(cmd, mi);
		return mi;
	}

	/**
	 * Fetch the menu item that was created for the given command.
	 * 
	 * @param cmd
	 *            Name of the action.
	 * @returns item created for the given command or null if one wasn't
	 *          created.
	 */
	protected JMenuItem getMenuItem(String cmd) {
		return (JMenuItem) menuItems.get(cmd);
	}

	protected Action getAction(String cmd) {
		return (Action) commands.get(cmd);
	}

	protected String getResourceString(String nm) {
		String str;
		try {
			str = resources.getString(nm);
		} catch (MissingResourceException mre) {
			str = null;
		}
		return str;
	}

	protected URL getResource(String key) {
		String name = getResourceString(key);
		if (name != null) {
			// URL url = this.getClass().getResource(name);
			URL url = getClass().getClassLoader().getResource(name);
			return url;
		}
		return null;
	}

	protected JToolBar getToolbar() {
		if(null==toolbar)
			toolbar=createToolbar();
		return toolbar;
	}

	protected JMenuBar getMenubar() {
		return menubar;
	}

	/**
	 * Create a status bar
	 */
	protected Component createStatusbar() {
		// need to do something reasonable here
		status = new StatusBar();
		return status;
	}

	/**
	 * Resets the undo manager.
	 */
	protected void resetUndoManager() {
		undo.discardAllEdits();
		undoAction.update();
		redoAction.update();
	}
	/**
	 * Create the toolbar. By default this reads the resource file for the
	 * definition of the toolbar.
	 */
	private JToolBar createToolbar() {
		if(null==toolbar) {
			toolbar = new JToolBar();
			String[] toolKeys = tokenize(getResourceString("toolbar"));
			for (int i = 0; i < toolKeys.length; i++) {
				if (toolKeys[i].equals("-")) {
					toolbar.add(Box.createHorizontalStrut(5));
				} else {
					toolbar.add(createTool(toolKeys[i]));
				}
			}
			toolbar.add(Box.createHorizontalGlue());
			messager.setPreferredSize(new Dimension(400,toolbar.getHeight()));
			messager.setHorizontalAlignment(SwingConstants.LEFT);
			toolbar.add(messager);
		}
		return toolbar;
	}

	/**
	 * Hook through which every toolbar item is created.
	 */
	protected Component createTool(String key) {
		return createToolbarButton(key);
	}

	/**
	 * Create a button to go inside of the toolbar. By default this will load an
	 * image resource. The image filename is relative to the classpath
	 * (including the '.' directory if its a part of the classpath), and may
	 * either be in a JAR file or a separate file.
	 * 
	 * @param key
	 *            The key in the resource file to serve as the basis of lookups.
	 */
	protected JButton createToolbarButton(String key) {
		URL url = getResource(key + imageSuffix);
		JButton b = new JButton(new ImageIcon(url)) {
			public float getAlignmentY() {
				return 0.5f;
			}
		};
		b.setRequestFocusEnabled(false);
		b.setMargin(new Insets(1, 1, 1, 1));

		String astr = getResourceString(key + actionSuffix);
		if (astr == null) {
			astr = key;
		}
		Action a = getAction(astr);
		if (a != null) {
			b.setActionCommand(astr);
			b.addActionListener(a);
		} else {
			b.setEnabled(false);
		}

		String tip = getResourceString(key + tipSuffix);
		if (tip != null) {
			b.setToolTipText(tip);
		}

		return b;
	}

	/**
	 * Take the given string and chop it up into a series of strings on
	 * whitespace boundaries. This is useful for trying to get an array of
	 * strings out of the resource file.
	 */
	protected String[] tokenize(String input) {
		Vector v = new Vector();
		StringTokenizer t = new StringTokenizer(input);
		String cmd[];

		while (t.hasMoreTokens())
			v.addElement(t.nextToken());
		cmd = new String[v.size()];
		for (int i = 0; i < cmd.length; i++)
			cmd[i] = (String) v.elementAt(i);

		return cmd;
	}

	/**
	 * Create the menubar for the app. By default this pulls the definition of
	 * the menu from the associated resource file.
	 */
	protected JMenuBar createMenubar() {
		JMenuBar mb = new JMenuBar();

		String[] menuKeys = tokenize(getResourceString("menubar"));
		for (int i = 0; i < menuKeys.length; i++) {
			JMenu m = createMenu(menuKeys[i]);
			if (m != null) {
				mb.add(m);
			}
		}
		this.menubar = mb;
		return mb;
	}

	/**
	 * Create a menu for the app. By default this pulls the definition of the
	 * menu from the associated resource file.
	 */
	protected JMenu createMenu(String key) {
		String[] itemKeys = tokenize(getResourceString(key));
		JMenu menu = new JMenu(getResourceString(key + labelSuffix));
		for (int i = 0; i < itemKeys.length; i++) {
			if (itemKeys[i].equals("-")) {
				menu.addSeparator();
			} else {
				JMenuItem mi = createMenuItem(itemKeys[i]);
				menu.add(mi);
			}
		}
		try {
			menu.setMnemonic(getResourceString(key+keySuffix).toCharArray()[0]);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return menu;
	}

	// Yarked from JMenu, ideally this would be public.
	protected PropertyChangeListener createActionChangeListener(JMenuItem b) {
		return new ActionChangedListener(b);
	}

	// Yarked from JMenu, ideally this would be public.
	private class ActionChangedListener implements PropertyChangeListener {
		JMenuItem menuItem;

		ActionChangedListener(JMenuItem mi) {
			super();
			this.menuItem = mi;
		}

		public void propertyChange(PropertyChangeEvent e) {
			String propertyName = e.getPropertyName();
			if (e.getPropertyName().equals(Action.NAME)) {
				String text = (String) e.getNewValue();
				menuItem.setText(text);
			} else if (propertyName.equals("enabled")) {
				Boolean enabledState = (Boolean) e.getNewValue();
				menuItem.setEnabled(enabledState.booleanValue());
			}
		}
	}

	private JTextComponent editor = createEditor();
	private JLabel messager = new JLabel("");
	private Hashtable commands;
	private Hashtable menuItems;
	private JMenuBar menubar;
	private JToolBar toolbar;
	private JComponent status;
	private JFrame elementTreeFrame;
	protected ElementTreePanel elementTreePanel;

	protected FileDialog fileDialog;

	/**
	 * Listener for the edits on the current document.
	 */
	protected UndoableEditListener undoHandler = new UndoHandler();

	/** UndoManager that we add edits to. */
	protected UndoManager undo = new UndoManager();

	/**
	 * Suffix applied to the key used in resource file lookups for an image.
	 */
	public static final String imageSuffix = "Image";

	/**
	 * Suffix applied to the key used in resource file lookups for a label.
	 */
	public static final String labelSuffix = "Label";

	/**
	 * Suffix applied to the key used in resource file lookups for an action.
	 */
	public static final String actionSuffix = "Action";
	public static final String keySuffix = "Key";

	/**
	 * Suffix applied to the key used in resource file lookups for tooltip text.
	 */
	public static final String tipSuffix = "Tooltip";

	public static final String openAction = "open";
	public static final String newAction = "new";
	public static final String saveAction = "save";
	public static final String exitAction = "exit";
	public static final String showElementTreeAction = "showElementTree";

	class UndoHandler implements UndoableEditListener {

		/**
		 * Messaged when the Document has created an edit, the edit is added to
		 * <code>undo</code>, an instance of UndoManager.
		 */
		public void undoableEditHappened(UndoableEditEvent e) {
			undo.addEdit(e.getEdit());
			undoAction.update();
			redoAction.update();
		}
	}

	/**
	 * FIXME - I'm not very useful yet
	 */
	class StatusBar extends JComponent {

		private static final long serialVersionUID = 1L;

		public StatusBar() {
			super();
			setLayout(new BoxLayout(this, BoxLayout.X_AXIS));
		}

		public void paint(Graphics g) {
			super.paint(g);
		}

	}

	// --- action implementations -----------------------------------

	private UndoAction undoAction = new UndoAction();
	private RedoAction redoAction = new RedoAction();

	/**
	 * Actions defined by the Notepad class
	 */
	private Action[] defaultActions = { new NewAction(), new OpenAction(),
			new SaveAction(), new ExitAction(), new ShowElementTreeAction(),
			undoAction, redoAction, new FindAction(getFrame(),messager, editor), new CommandAction(messager, editor) };

	class UndoAction extends AbstractAction {

		private static final long serialVersionUID = 1L;

		public UndoAction() {
			super("Undo");
			setEnabled(false);
		}

		public void actionPerformed(ActionEvent e) {
			try {
				undo.undo();
			} catch (CannotUndoException ex) {
				System.out.println("Unable to undo: " + ex);
				ex.printStackTrace();
			}
			update();
			redoAction.update();
		}

		protected void update() {
			if (undo.canUndo()) {
				setEnabled(true);
				putValue(Action.NAME, undo.getUndoPresentationName());
			} else {
				setEnabled(false);
				putValue(Action.NAME, "Undo");
			}
		}
	}

	class RedoAction extends AbstractAction {

		private static final long serialVersionUID = 1L;

		public RedoAction() {
			super("Redo");
			setEnabled(false);
		}

		public void actionPerformed(ActionEvent e) {
			try {
				undo.redo();
			} catch (CannotRedoException ex) {
				System.out.println("Unable to redo: " + ex);
				ex.printStackTrace();
			}
			update();
			undoAction.update();
		}

		protected void update() {
			if (undo.canRedo()) {
				setEnabled(true);
				putValue(Action.NAME, undo.getRedoPresentationName());
			} else {
				setEnabled(false);
				putValue(Action.NAME, "Redo");
			}
		}
	}

	class OpenAction extends NewAction {

		private static final long serialVersionUID = 1L;

		OpenAction() {
			super(openAction);
		}

		public void actionPerformed(ActionEvent e) {
			Frame frame = getFrame();
			JFileChooser chooser = new JFileChooser();
			int ret = chooser.showOpenDialog(frame);

			if (ret != JFileChooser.APPROVE_OPTION) {
				return;
			}

			File f = chooser.getSelectedFile();
			if (f.isFile() && f.canRead()) {
				Document oldDoc = getEditor().getDocument();
				if (oldDoc != null)
					oldDoc.removeUndoableEditListener(undoHandler);
				if (elementTreePanel != null) {
					elementTreePanel.setEditor(null);
				}
				getEditor().setDocument(new PlainDocument());
				frame.setTitle(f.getName());
				Thread loader = new FileLoader(f, editor.getDocument());
				loader.start();
			} else {
				JOptionPane.showMessageDialog(getFrame(),
						"Could not open file: " + f, "Error opening file",
						JOptionPane.ERROR_MESSAGE);
			}
		}
	}

	class SaveAction extends AbstractAction {

		private static final long serialVersionUID = 1L;

		SaveAction() {
			super(saveAction);
		}

		public void actionPerformed(ActionEvent e) {
			Frame frame = getFrame();
			JFileChooser chooser = new JFileChooser();
			int ret = chooser.showSaveDialog(frame);

			if (ret != JFileChooser.APPROVE_OPTION) {
				return;
			}

			File f = chooser.getSelectedFile();
			frame.setTitle(f.getName());
			Thread saver = new FileSaver(f, editor.getDocument());
			saver.start();
		}
	}

	class NewAction extends AbstractAction {

		private static final long serialVersionUID = 1L;

		NewAction() {
			super(newAction);
		}

		NewAction(String nm) {
			super(nm);
		}

		public void actionPerformed(ActionEvent e) {
			Document oldDoc = getEditor().getDocument();
			if (oldDoc != null)
				oldDoc.removeUndoableEditListener(undoHandler);
			getEditor().setDocument(new PlainDocument());
			getEditor().getDocument().addUndoableEditListener(undoHandler);
			resetUndoManager();
			getFrame().setTitle(resources.getString("Title"));
			revalidate();
		}
	}

	/**
	 * Really lame implementation of an exit command
	 */
	class ExitAction extends AbstractAction {

		private static final long serialVersionUID = 1L;

		ExitAction() {
			super(exitAction);
		}

		public void actionPerformed(ActionEvent e) {
			System.exit(0);
		}
	}

	/**
	 * Action that brings up a JFrame with a JTree showing the structure of the
	 * document.
	 */
	class ShowElementTreeAction extends AbstractAction {

		private static final long serialVersionUID = 1L;

		ShowElementTreeAction() {
			super(showElementTreeAction);
		}

		ShowElementTreeAction(String nm) {
			super(nm);
		}

		public void actionPerformed(ActionEvent e) {
			if (elementTreeFrame == null) {
				// Create a frame containing an instance of
				// ElementTreePanel.
				try {
					String title = resources.getString("ElementTreeFrameTitle");
					elementTreeFrame = new JFrame(title);
				} catch (MissingResourceException mre) {
					elementTreeFrame = new JFrame();
				}

				elementTreeFrame.addWindowListener(new WindowAdapter() {
					public void windowClosing(WindowEvent weeee) {
						elementTreeFrame.setVisible(false);
					}
				});
				Container fContentPane = elementTreeFrame.getContentPane();

				fContentPane.setLayout(new BorderLayout());
				elementTreePanel = new ElementTreePanel(getEditor());
				fContentPane.add(elementTreePanel);
				elementTreeFrame.pack();
			}
			elementTreeFrame.show();
		}
	}

	/**
	 * Thread to load a file into the text storage model
	 */
	class FileLoader extends Thread {

		FileLoader(File f, Document doc) {
			setPriority(4);
			this.f = f;
			this.doc = doc;
		}

		public void run() {
			try {
				// initialize the statusbar
				status.removeAll();
				JProgressBar progress = new JProgressBar();
				progress.setMinimum(0);
				progress.setMaximum((int) f.length());
				status.add(progress);
				status.revalidate();

				// try to start reading
				Reader in = new FileReader(f);
				char[] buff = new char[4096];
				int nch;
				while ((nch = in.read(buff, 0, buff.length)) != -1) {
					doc.insertString(doc.getLength(), new String(buff, 0, nch),
							null);
					progress.setValue(progress.getValue() + nch);
				}
			} catch (IOException e) {
				final String msg = e.getMessage();
				SwingUtilities.invokeLater(new Runnable() {
					public void run() {
						JOptionPane
								.showMessageDialog(getFrame(),
										"Could not open file: " + msg,
										"Error opening file",
										JOptionPane.ERROR_MESSAGE);
					}
				});
			} catch (BadLocationException e) {
				System.err.println(e.getMessage());
			}
			doc.addUndoableEditListener(undoHandler);
			// we are done... get rid of progressbar
			status.removeAll();
			status.revalidate();

			resetUndoManager();

			if (elementTreePanel != null) {
				SwingUtilities.invokeLater(new Runnable() {
					public void run() {
						elementTreePanel.setEditor(getEditor());
					}
				});
			}
		}

		Document doc;
		File f;
	}

	/**
	 * Thread to save a document to file
	 */
	class FileSaver extends Thread {
		Document doc;
		File f;

		FileSaver(File f, Document doc) {
			setPriority(4);
			this.f = f;
			this.doc = doc;
		}

		public void run() {
			try {
				// initialize the statusbar
				status.removeAll();
				JProgressBar progress = new JProgressBar();
				progress.setMinimum(0);
				progress.setMaximum((int) doc.getLength());
				status.add(progress);
				status.revalidate();

				// start writing
				Writer out = new FileWriter(f);
				Segment text = new Segment();
				text.setPartialReturn(true);
				int charsLeft = doc.getLength();
				int offset = 0;
				while (charsLeft > 0) {
					doc.getText(offset, Math.min(4096, charsLeft), text);
					out.write(text.array, text.offset, text.count);
					charsLeft -= text.count;
					offset += text.count;
					progress.setValue(offset);
					try {
						Thread.sleep(10);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
				out.flush();
				out.close();
			} catch (IOException e) {
				final String msg = e.getMessage();
				SwingUtilities.invokeLater(new Runnable() {
					public void run() {
						JOptionPane.showMessageDialog(getFrame(),
								"Could not save file: " + msg,
								"Error saving file", JOptionPane.ERROR_MESSAGE);
					}
				});
			} catch (BadLocationException e) {
				System.err.println(e.getMessage());
			}
			// we are done... get rid of progressbar
			status.removeAll();
			status.revalidate();
		}
	}
}
